# -*- coding: utf-8 -*-
# 创建时间:2024/9/21 下午6:02
# 创建人:天霄
# 基于 Python 3.11
# ========================================
# 监控手机电量，电量不足则充电，电量已满则断电。同时发送邮件通知。
# ========================================
print('启动中...')
import os
import sys
from pathlib import Path

# 保证无论在哪个目录执行此脚本，工作目录都是相同的
workPath = Path(__file__).parent.absolute()
cwd = Path.cwd()
print(f'当前工作目录：{workPath}')
if cwd != workPath:
    print(f'更改工作目录为：{workPath}')
    os.chdir(workPath)
    print(f'当前工作目录：{Path.cwd()}')

conf_path = workPath / 'config.yaml'
if not conf_path.is_file():
    print('配置文件 config.yaml 不存在，请从 config-template.yaml 复制一份并重命名为 config.yaml')
    sys.exit(1)

import time
import yaml
import termux
import datetime
import threading
from miio import Device
from common.emailer import EMailer
from common.utils import simplify_traceback, init_db, insert_battery_info, insert_charge_operation, delete_old_records

conf: dict = yaml.safe_load(conf_path.read_text(encoding='utf-8'))
is_to_db = conf.get('to_db', True)  # 是否将电量记录写入数据库
device_conf = conf.get('device', {})
device = Device(device_conf.get('ip'), device_conf.get('token'))


def _deviceControl(isOn: bool):
    """控制设备是否上电"""
    power_param = {
        'did': str(device_conf.get('did', '')),
        'siid': device_conf.get('siid'),
        'piid': device_conf.get('piid'),
        'value': isOn
    }
    device.send("set_properties", [power_param])


def quickSendEMail(subject: str, content: str):
    """快速发送邮件"""
    email_conf = conf.get('email', {})
    if not email_conf.get('open', False):
        print(str(datetime.datetime.now()).center(50, '-') + f'\n【主题】{subject}\n【内容】{content}\n' + '-' * 50)
        return
    receivers = email_conf.get('receivers')
    sender = email_conf.get('sender')
    mailer = EMailer(email_conf.get('host'), email_conf.get('username'), email_conf.get('password'))
    mailer.send_email(receivers, subject=subject, mail_msg=content, sender=sender)


def powerOn(retry=10, frequency=5, is_send_email=True):
    """上电。返回是否上电成功

    - 上电命令执行最大重试 ``retry`` 次，全部失败则邮件通知
    - 上电命令执行成功后，等待 ``check_wait`` 分钟，检查电量是否增加，若未增加则下电后重新上电。此复检操作最多重试 ``check_retry`` 次

    :param retry: 上电命令执行最大重试次数
    :param frequency: 上电命令执行失败后重试等待时间，秒
    :param is_send_email: 是否发送邮件通知，默认是
    :return: 是否上电成功
    """
    assert retry > 0
    assert frequency >= 0

    _deviceControl(False)
    time.sleep(3)
    before_current: int = termux.API.battery()[1]['current']
    for _ in range(max(retry, 1)):
        _deviceControl(True)
        time.sleep(5)
        battery_info = termux.API.battery()[1]
        percentage: int = battery_info['percentage']
        status: str = battery_info['status']
        current: int = battery_info['current']
        if is_to_db:
            insert_charge_operation(True, percentage, current)
        # 状态不对/电流为0/电流方向不变：上电失败
        if status != 'CHARGING' or current == 0 or (before_current < 0 and current < 0) or (before_current > 0 and current > 0):
            _deviceControl(False)
            time.sleep(frequency)
            continue
        if is_send_email:
            quickSendEMail('[本机]状态-充电中',
                           f'上电成功！\n当前电量: {percentage}%，当前状态：{status}，当前电流：{current / 1000_000}A')
        return True

    if is_send_email:
        quickSendEMail(f'[本机]状态-电量低（{percentage}%）！',
                       f'上电失败！\n当前电量: {percentage}%，当前状态：{status}，当前电流：{current / 1000_000}A')
    return False


def powerOff(retry=10, frequency=5, is_send_email=True):
    """下电。返回是否下电成功"""
    for i in range(1, max(retry + 1, 2)):
        _deviceControl(False)
        time.sleep(5)
        battery_info = termux.API.battery()[1]
        percentage = battery_info['percentage']
        status = battery_info['status']
        current = battery_info['current']
        if is_to_db:
            insert_charge_operation(False, percentage, current)
        if status in ('CHARGING', 'FULL'):
            time.sleep(frequency)
            continue
        else:
            if is_send_email:
                quickSendEMail('[本机]状态-正常', f'下电成功！总计尝试{i}次。\n当前电量: {percentage}%，当前状态：{status}')
            return True
    if is_send_email:
        quickSendEMail(f'[本机]状态-满溢充电（{percentage}%）！', f'下电失败！总计尝试{i}次。\n当前电量: {percentage}%，当前状态：{status}')
    return False


def battery_check_forever():
    """循环轮询手机电量进行控制"""
    frequency = conf.get('frequency', 10)  # 轮询频率
    batteryLimit = conf.get('batteryLimit', 20)  # 电量不足预警的下限值
    batteryFull = conf.get('batteryFull', 100)  # 电量充足预警的上限值
    warningInterval = conf.get('warningInterval', 30)  # 预警间隔时间（分钟）
    keep_db_days = conf.get('keep_db_days', 10)  # 数据库中保留多少天的电量记录
    if is_to_db:
        init_db()

    last_battery_low_time = 0  # 上一次电量不足的时刻
    last_battery_full_time = 0  # 上一次电量充足的时刻
    today_cleared = {'date': datetime.datetime.now().date(), 'clear': False}

    def _inner_recorder():
        """电量记录器"""
        _battery = {"health": "GOOD", "percentage": -1, "plugged": "UNPLUGGED", "status": "DISCHARGING",
                    "temperature": 0, "current": 0}  # 每次轮询读取的电量信息，初始示例格式
        lock = threading.Lock()

        def _record_battery(battery_info: dict):
            """记录当前电量信息"""
            nonlocal _battery
            with lock:
                _battery = battery_info

        def _read_battery():
            """读取当前电量信息"""
            with lock:
                return _battery.copy()

        return _record_battery, _read_battery

    record_battery, read_battery = _inner_recorder()

    def task_read_battery():
        """读取和记录当前电量信息的线程任务"""
        lock = threading.Lock()
        while True:
            time.sleep(frequency)
            _battery_info: dict = termux.API.battery()[1]
            record_battery(_battery_info)
            if is_to_db:
                with lock:
                    insert_battery_info(_battery_info)
                    now = datetime.datetime.now()
                    if now.date() != today_cleared['date']:
                        today_cleared['date'] = now.date()
                        today_cleared['clear'] = False
                    # 每天凌晨1点删除旧记录
                    if now.hour == 1 and not today_cleared['clear']:
                        delete_old_records(keep_db_days)
                        today_cleared['clear'] = True

    threading.Thread(target=task_read_battery, daemon=True).start()
    while True:
        time.sleep(frequency)
        battery_info = read_battery()
        if battery_info['percentage'] == -1:  # 初始状态，未读取到电量信息
            continue
        percentage: int = battery_info['percentage']
        status: str = battery_info['status']
        current: int = battery_info['current']
        if percentage <= 10:
            quickSendEMail(f'[本机]状态-电量极低({percentage}%)！！！',
                           f'电量极低，请尽快充电！！！必要时请手动处理！\n当前电量: {percentage}%，当前状态：{status}，电流：{current}μA。')
        if percentage < batteryLimit:  # 小于阈值，且未充电，且距离上次预警已超过xx分钟：则再次上电
            if status != 'CHARGING' and time.time() - last_battery_low_time > warningInterval * 60:
                powerOn()
                last_battery_low_time = time.time()
        elif percentage >= batteryFull:  # 满电量，且充电中，且距离上次预警已超过xx分钟：则再次下电
            if status in ('CHARGING', 'FULL') and time.time() - last_battery_full_time > warningInterval * 60:
                powerOff()
                last_battery_full_time = time.time()
        else:  # 电量在阈值~满电量之间
            if status == 'CHARGING' and abs(current) <= 2000:  # 充电状态，但电流小于2000μA：异常充电情况，无法正常充电，尝试重新上下电
                if not powerOff(retry=1, is_send_email=False):
                    quickSendEMail('[本机]状态-充电异常',
                                   f'充电异常，充电电流低！尝试下电失败！\n当前电量: {percentage}%，当前状态：{status}，电流：{current}μA。')
                    continue
                time.sleep(3)
                if not powerOn(retry=1, is_send_email=False):
                    quickSendEMail('[本机]状态-充电异常',
                                   f'充电异常，充电电流低！尝试上电失败！\n当前电量: {percentage}%，当前状态：{status}，电流：{current}μA。')
                else:
                    batt: dict = termux.API.battery()[1]
                    quickSendEMail(
                        '[本机]状态-充电异常已处理',
                        f"充电异常，充电电流低（{current}μA），但已重新上下电。\n"
                        f"当前电量: {batt['percentage']}%，当前状态：{batt['status']}，电流：{batt['current']}μA。")



if __name__ == '__main__':
    time.sleep(5)
    try:
        battery_check_forever()
    except Exception as err:
        err_msg = f'发生异常: {simplify_traceback(err)}'
        print(err_msg)
        quickSendEMail('[本机]脚本异常', err_msg)
    except KeyboardInterrupt:
        print('\n[KeyboardInterrupt]\n')
    finally:
        sys.exit(0)
